package com.aionemu.packetsamurai;

import java.io.IOException;
import java.net.InetAddress;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import javolution.util.FastMap;
import jpcap.JpcapCaptor;
import jpcap.NetworkInterface;
import jpcap.NetworkInterfaceAddress;
import jpcap.PacketReceiver;
import jpcap.packet.TCPPacket;


import com.aionemu.packetsamurai.gui.ChoiceDialog;
import com.aionemu.packetsamurai.protocol.Protocol;
import com.aionemu.packetsamurai.protocol.ProtocolManager;

/**
 * 
 * @author Ulysses R. Ribeiro
 * @author Gilles Duboscq
 *
 */
public class Captor implements Runnable
{

    protected JpcapCaptor _packetCaptor;
    protected NetworkInterfaceAddress[] _networkAddress;
    private boolean _captorEnabled = false;
    private static final int SNAPSHOT_LENGHT = 4*65536;
    private int _deviceId = -1;
    protected PacketReceiver _packetHandler;
    protected static Map<Integer, Protocol> _activeProtocols;
    
    private static class SingletonHolder
	{
		private final static Captor singleton = Captor.initCaptor();
	}
	
	public static Captor getInstance()
	{
		return SingletonHolder.singleton;
	}

    private static Captor initCaptor()
    {
        try
        {
            Captor captor = new Captor();
            captor.initDevice();
            Thread captorThread = new Thread(captor, "CaptorThread");
            captorThread.setPriority(Thread.MAX_PRIORITY);
            captorThread.start();
            return captor;
        }
        catch (Exception e)
        {
            Captor.showSetActiveProtocols();
            
            // try to init again (will ask user to set procotol agains if he canceled this previous one)
            return Captor.initCaptor();
        }
    }
    
    public Captor()
    {
        _packetHandler = new PacketHandler(this);
    }
    
    public void initDevice() throws Exception
    {
        this.configureProtocols();
        
        NetworkInterface[] niList = null;
        try
        {
        	niList = JpcapCaptor.getDeviceList();
        }
        catch(UnsatisfiedLinkError ule)
        {
        	PacketSamurai.getUserInterface().log("ERROR: You are missing the JPcap lib :\n"+ule.getLocalizedMessage());
        	setCaptor(false);
        	return;
        }
        int deviceCount = niList.length;
        // Check if there is a device to sniff
        if (deviceCount <= 0)
        {
            setCaptor(false);
            PacketSamurai.getUserInterface().log("ERROR: No Network Interfaces have been found!");
        }
        //if there is only one we bind it
        else if (deviceCount == 1)
        {
            openDevice(niList[0]);
        }
        else
        {
            if (!PacketSamurai.configExists("NetworkInterface"))
            {
                selectNetWorkInterface();
            }
            else
            {
                int deviceNumber = Integer.parseInt(PacketSamurai.getConfigProperty("NetworkInterface"));
                openDevice(deviceNumber);
            }
        }
    }

    public void selectNetWorkInterface()
    {
        NetworkInterface niList[] = JpcapCaptor.getDeviceList();
        int deviceCount = niList.length;
        String nameList[] = new String[deviceCount];
        for (int i = 0; i < deviceCount; i++)
        {

            PacketSamurai.getUserInterface().log("Found Interface: "+niList[i].description);
            nameList[i]=(niList[i].addresses.length >= 1 ? niList[i].addresses[0].address.getHostAddress()+": " : "")
            +niList[i].description;
        }
        String[][] choices = new String[1][];
        choices[0] = nameList;
        
        int[] ret = ChoiceDialog.choiceDialog("Select Interface for Sniffing", new String[]{"Interfaces"} , choices);
        if (ret != null)
        {
            this.openDevice(ret[0]);
            PacketSamurai.setConfigProperty("NetworkInterface",Integer.toString(ret[0]));
        }
        else
        {
            PacketSamurai.getUserInterface().log("No interface selected.");
        }
        // TODO delete its stuff
        //Main.getInstance().showInterfaceSelector(nameList);
    }

    public void openDevice(int deviceNumber)
    {
        openDevice(JpcapCaptor.getDeviceList()[deviceNumber]);
        _deviceId = deviceNumber;
    }
    
    private void configureProtocols() throws Exception
    {
        Captor.setActiveProtocols(PacketSamurai.loadSnifferActiveProtocols());
        if (Captor.getActiveProtocols() == null)
        {
            Captor.setActiveProtocols(new FastMap<Integer, Protocol>());
            
            for (Protocol p : ProtocolManager.getInstance().getProtocols())
            {
                if (Captor.getActiveProtocols().containsKey(p.getPort()))
                {
                    // invalidate the map being built
                    Captor.setActiveProtocols(null);
                    throw new Exception("More then one protocol with same port, only one protocol per port can be active for the sniffer.");
                }
                Captor.getActiveProtocols().put(p.getPort(), p);
            }
        }

        
    }
    
    public void openDevice(NetworkInterface ni)
    {
        try
        {
            if (_packetCaptor != null)
            {
                setCaptor(false);
                _packetCaptor.close();
            }
            _packetCaptor = JpcapCaptor.openDevice(ni,SNAPSHOT_LENGHT,false,10);
            
            setCaptor(true);
            _networkAddress = ni.addresses;
            PacketSamurai.getUserInterface().log("Successfully opened device ("+ni.description+").");
            
            
            Set<Integer> ports = Captor.getActiveProtocols().keySet();
            Iterator<Integer> i = ports.iterator();

            String filter = PacketSamurai.getConfigProperty("filter", "").trim();
            if (filter.length() > 0)
            {
                PacketSamurai.getUserInterface().log("Sniffing with filter: "+filter);
                _packetCaptor.setFilter(filter, false);
            }
            else if (i.hasNext())
            {
                StringBuilder sb = new StringBuilder("(tcp port");
                StringBuilder portsSB = new StringBuilder();
                for(; i.hasNext();)
                {
                    Integer port = i.next();
                    sb.append(" "+port+")");
                    portsSB.append(port);
                    
                    if(i.hasNext())
                    {
                        portsSB.append(' ');
                        sb.append(" or (tcp port");
                    }
                }
                PacketSamurai.getUserInterface().log("Sniffing with filter: "+sb.toString());
                _packetCaptor.setFilter(sb.toString(),false);
                PacketSamurai.getUserInterface().log("Sniffing on port(s): "+portsSB);
            }
        }
        catch (IOException ioe)
        {
            PacketSamurai.getUserInterface().log("ERROR: Failed to open device ("+ni.description+") for capture "+ioe);
        }
    }


    public void setCaptor(boolean val)
    {
        if (val == false && _captorEnabled == true)
        {
            _packetCaptor.breakLoop();
        }
        _captorEnabled = val;
    }
    
    public boolean isCaptorEnabled()
	{
		return _captorEnabled;
	}

    public void run()
    {
    	if(!this.isCaptorEnabled())
    		return;
        // capture packets indefinitely
        _packetCaptor.loopPacket( -1, _packetHandler);
    }
    
    public JpcapCaptor getPcapCaptor()
    {
        return _packetCaptor;
    }

    public boolean isClientAddress(InetAddress address)
    {
        for (int i = 0; i < _networkAddress.length; i++)
        {
            if (_networkAddress[i].address.equals(address))
                return true;
        }
        return false;
    }

    public int getCurrentDeviceId()
    {
        return _deviceId;
    }

    public static void setActiveProtocols(Map<Integer, Protocol> activeProtocols)
    {
        _activeProtocols = activeProtocols;
    }
    
    public static Protocol getActiveProtocolForPort(int port)
    {
        return Captor.getActiveProtocols().get(port);
    }
    
    protected static Map<Integer, Protocol> getActiveProtocols()
    {
        return _activeProtocols;
    }
    
    public String getPacketDump(TCPPacket tcpPacket)
    {
        StringBuffer sb = new StringBuffer();

        sb.append("Received a packet: "+PacketSamurai.hexDump(tcpPacket.data)+ " - Flags: ");
        sb.append(" - ACK: "+(tcpPacket.ack ? "1 : "+tcpPacket.ack_num : "0"));
        sb.append(" - Fin: "+(tcpPacket.fin ? "1" : "0"));
        sb.append(" - SYN: "+(tcpPacket.syn ? "1" : "0"));
        sb.append(" - FIN: "+(tcpPacket.fin ? "1" : "0"));
        sb.append(" - RST: "+(tcpPacket.rst ? "1" : "0"));
        sb.append(" - Seq:"+tcpPacket.sequence);
        sb.append(" - "+(this.isClientAddress(tcpPacket.src_ip) ? "C->S" : "S->C"));
        sb.append(" - psh: "+(tcpPacket.psh ? "1" : "0"));
        sb.append(" - Delay: "+(tcpPacket.d_flag ? "1" : "0"));
        sb.append(" - Dont Fragment: "+(tcpPacket.dont_frag ? "1" : "0"));
        sb.append(" - More Fragment: "+(tcpPacket.more_frag ? "1" : "0"));
        sb.append(" - Realibility: "+(tcpPacket.more_frag ? "1" : "0"));
        sb.append(" - Frag Reservation: "+(tcpPacket.rsv_frag ? "1" : "0"));
        
        return sb.toString();
    }
    
    public static void showSetActiveProtocols()
    {
        int total = ProtocolManager.getInstance().getProtocolsByPort().size();
        String[] titles = new String[total];
        String[][] choices = new String[total][];
        Protocol[][] protocols = new Protocol[total][];
        int i = 0;
        for (int port : ProtocolManager.getInstance().getProtocolsByPort().keySet())
        {
            Set<Protocol> prots = ProtocolManager.getInstance().getProtocolForPort(port);
            titles[i] = "Port "+port;
            int count = prots.size();
            int j = 0;
            choices[i] = new String[count];
            protocols[i] = new Protocol[count];
            for (Protocol prot : prots)
            {
                protocols[i][j] = prot;
                choices[i][j++] = prot.getName();
            }
            i++;
        }
        PacketSamurai.getUserInterface().log("Please select the active protocols for Sniffing, non active protocols are used for opening old logs.");
        int[] ret = ChoiceDialog.choiceDialog("Select Active Protocols for Sniffing", titles, choices);
        
        // u are doomed to properly set it
        if (ret != null)
        {
            Map<Integer,Protocol> activeProtocols = new FastMap<Integer, Protocol>();
            i = 0;
            for (int sel : ret)
            {
                Protocol p = protocols[i++][sel];
                activeProtocols.put(p.getPort(), p);
            }
            Captor.setActiveProtocols(activeProtocols);
            PacketSamurai.saveSnifferActiveProtocols();
        }
    }
}
